package com.siao.net.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.ShutdownChannelGroupException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author SiaoLeon
 * Socket服务器
 */
public final class SocketServer extends ConnectionService {

	private static final Logger logger = LoggerFactory.getLogger(SocketServer.class);

	/**
	 * Socket地址
	 */
	private final InetSocketAddress address;

	/**
	 * 线程数
	 */
	private final Integer threadCount;

	/**
	 * 连接集合
	 */
	private final CopyOnWriteArrayList<Connection> connections;

	/**
	 * Socket服务器通道组
	 */
	private AsynchronousChannelGroup channelGroup;

	/**
	 * Socket服务器通道
	 */
	private AsynchronousServerSocketChannel serverChannel;

	/**
	 * 构造函数
	 * @param port
	 */
	public SocketServer(int port) {
		this(port, Runtime.getRuntime().availableProcessors());
	}

	/**
	 * 构造函数
	 * @param port
	 * @param threadCount
	 */
	public SocketServer(int port, int threadCount) {
		this.address = new InetSocketAddress(port);
		this.threadCount = threadCount;
		this.connections = new CopyOnWriteArrayList<Connection>();
	}

	/**
	 * 启动
	 */
	public synchronized final void start() {
		try {
			this.channelGroup = AsynchronousChannelGroup.withFixedThreadPool(this.threadCount, Executors.defaultThreadFactory());

			this.serverChannel = AsynchronousServerSocketChannel.open(this.channelGroup);
			this.serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
			this.serverChannel.bind(this.address);

			this.accept();

			logger.info("SocketServer is started. bind port:" + this.address.getPort());
		} catch (IOException e) {
			logger.error("SocketServer have failed to start : " + e.getMessage());
			this.stop();
		}
	}

	/**
	 * 停止
	 */
	public synchronized final void stop() {
		try {
			for (Connection connection : this.connections) {
				connection.close();
			}
			this.connections.clear();

			if (this.serverChannel != null) {
				if (this.serverChannel.isOpen()) {
					this.serverChannel.close();
				}
				this.serverChannel = null;
			}

			if (this.channelGroup != null) {
				if (!this.channelGroup.isShutdown()) {
					this.channelGroup.shutdown();
				}
				this.channelGroup = null;
			}
		} catch (IOException e) {
			e.printStackTrace();
			logger.info("SocketServer have failed to stop.");
		}
		logger.info("SocketServer is stopped.");
	}

	/**
	 * 接受连接
	 */
	private void accept() {
		if (!this.serverChannel.isOpen()) {
			return;
		}

		this.serverChannel.accept(new Connection(this), new ConnectionAcceptHandler());
	}

	/**
	 * @author SiaoLeon
	 * 连接处理
	 */
	class ConnectionAcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Connection> {

		@Override
		public void completed(AsynchronousSocketChannel channel, Connection connection) {
			connection.register(channel);
			SocketServer.this.accept();
		}

		@Override
		public void failed(Throwable exc, Connection connection) {
			if (exc instanceof ShutdownChannelGroupException) {
				return;
			}
			if (exc instanceof AsynchronousCloseException) {
				return;
			}

			SocketServer.this.connectionFailed(connection);
			SocketServer.this.accept();

			exc.printStackTrace();
			logger.error(exc.getMessage());
		}

	}

	@Override
	protected void connectionRegister(Connection connection) {
		this.connections.add(connection);
		super.connectionRegister(connection);
	}

	@Override
	protected void connectionFailed(Connection connection) {
		super.connectionFailed(connection);
	}

	@Override
	protected void connectionSend(Connection connection, IDataPack data) {
		super.connectionSend(connection, data);
	}

	@Override
	protected void connectionReceive(Connection connection, Object data) {
		super.connectionReceive(connection, data);
	}

	@Override
	protected void connectionClose(Connection connection) {
		super.connectionClose(connection);
	}

	@Override
	protected void connectionDisconnect(Connection connection) {
		super.connectionDisconnect(connection);
		this.connections.remove(connection);
	}

}
